/* @flow */

import config from '../../core/config'
import { warn, cached } from '../../core/util/index'
import { mark, measure } from '../../core/util/perf'

import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

const idToTemplate = cached(id => {
    const el = query(id)
    return el && el.innerHTML
})
// 1.先获取的运行时(runtime only)的$mount
const mount = Vue.prototype.$mount

//2.再运行时版本的基础上，再定义完整版（runtime+compile）的$mount
Vue.prototype.$mount = function (el?: string | Element, hydrating?: boolean): Component {
    //完整版本和只包含运行时版本之间的差异主要在于是否有模板编译阶段，
    // 只包含运行时版本没有模板编译阶段，初始化阶段完成后直接进入挂载阶段，
    // 而完整版本是初始化阶段完成后进入模板编译阶段，然后再进入挂载阶段。
    // 也就是说，这两个版本最终都会进入挂载阶段。
    // 所以在完整版本的$mount方法中将模板编译完成后需要回头去调只包含运行时版本的$mount方法以进入挂载阶段

    // 获取el元素
    el = el && query(el)

    // vue 不可以直接挂在到body 或者 html 上， 因为el会替换html/body
    // 这样会导致整个页面没有html 或 body
    if (el === document.body || el === document.documentElement) {
        process.env.NODE_ENV !== 'production' && warn(
            `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
        )
        return this
    }

    const options = this.$options
    // resolve template/el and convert to render function
    //  把templat/el 转化为 render 函数
    if (!options.render) { //如果没有手写render 函数
        let template = options.template
        if (template) { // 在template 的情况下，如果没有，那就直接获取外部模板
            if (typeof template === 'string') { // 对 template 处理
                if (template.charAt(0) === '#') { // 如果是id ,通过id方式获取innerhtml
                    template = idToTemplate(template)
                    /* istanbul ignore if */
                    if (process.env.NODE_ENV !== 'production' && !template) {
                        warn(
                            `Template element not found or is empty: ${options.template}`,
                            this
                        )
                    }
                }
            } else if (template.nodeType) { // 如果是dom 对象， 那就直接获取innerhtml
                template = template.innerHTML
            } else {
                if (process.env.NODE_ENV !== 'production') {
                    warn('invalid template option:' + template, this)
                }
                return this
            }
        } else if (el) {
            //   获取节点
            template = getOuterHTML(el)
        }
        if (template) { //   编译
            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                mark('compile')
            }

            const { render, staticRenderFns } = compileToFunctions(template, {
                outputSourceRange: process.env.NODE_ENV !== 'production',
                shouldDecodeNewlines,
                shouldDecodeNewlinesForHref,
                delimiters: options.delimiters,
                comments: options.comments
            }, this)
            options.render = render
            options.staticRenderFns = staticRenderFns

            /* istanbul ignore if */
            if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
                mark('compile end')
                measure(`vue ${this._name} compile`, 'compile', 'compile end')
            }
        }
    }
    return mount.call(this, el, hydrating)
}

/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */
function getOuterHTML (el: Element): string {
    //  如果有outerHTML， 则使用outerHTML，没有，则在外面包一层
    if (el.outerHTML) {
        return el.outerHTML
    } else {
        const container = document.createElement('div')
        container.appendChild(el.cloneNode(true))
        return container.innerHTML
    }
}

Vue.compile = compileToFunctions

export default Vue
